import WebGLRenderer from './webglrenderer';

function toMathArray(input) {
  // console.log(input.__proto__);
  const min = 0;
  let max;
  if (input instanceof Uint8Array || input instanceof Uint8ClampedArray) {
    max = 255;
  } else if (input instanceof Uint16Array) {
    max = 65535;
  } else if (input instanceof Uint32Array) {
    max = 4294967295;
  }
  // TODO: more types

  const out = new Float32Array(input.length);
  for (let i = 0; i < out.length; ++i) {
    out[i] = input[i] / max;
  }
  return out;
}

function toOriginalArray(input, Type) {
  let max = 0;
  switch (Type) {
    case Uint8Array:
    case Uint8ClampedArray:
      max = 255;
      break;
    case Uint16Array:
      max = 65535;
      break;
    case Uint32Array:
      max = 4294967295;
      break;
    default:
      throw new Error(`Unsupported array type ${Type}`);
  }

  const out = new Type(input.length);
  for (let i = 0; i < out.length; ++i) {
    out[i] = Math.round(input[i] * max);
  }
  return out;
}

function sigmoidalContrast(data, contrast, bias) {
  const alpha = bias;
  const beta = contrast;

  if (beta > 0) {
    const denominator = 1 / (1 + Math.exp(beta * (alpha - 1))) - 1 / (1 + Math.exp(beta * alpha));
    for (let i = 0; i < data.length; ++i) {
      const numerator = 1 / (1 + Math.exp(beta * (alpha - data[i]))) - 1 / (1 + Math.exp(beta * alpha));
      data[i] = numerator / denominator;
    }
  } else {
    for (let i = 0; i < data.length; ++i) {
      data[i] = (
        (beta * alpha) - Math.log(
          (
            1 / (
              (data[i] / (1 + Math.exp((beta * alpha) - beta))) -
              (data[i] / (1 + Math.exp(beta * alpha))) +
              (1 / (1 + Math.exp(beta * alpha)))
            )
          ) - 1)
      ) / beta;
    }
  }
  return data;
}
function gamma(data, g) {
  for (let i = 0; i < data.length; ++i) {
    data[i] **= (1 / g);
  }
  return data;
}
function blitChannels(canvas, width, height, red, green, blue) {
  const ctx = canvas.getContext('2d');
  const id = ctx.createImageData(width, height);
  const o = id.data;
  for (let i = 0; i < id.data.length / 4; ++i) {
    o[i * 4] = red[i];
    o[(i * 4) + 1] = green[i];
    o[(i * 4) + 2] = blue[i];
    o[(i * 4) + 3] = (!red[i] && !green[i] && !blue[i]) ? 0 : 255;
  }
  ctx.putImageData(id, 0, 0);
}

function renderData2d(canvas, pipeline, width, height, redData, greenData, blueData) {
  let [red, green, blue] = [
    toMathArray(redData),
    toMathArray(greenData),
    toMathArray(blueData),
  ];

  let bands = [red, green, blue];

  for (const step of pipeline) {
    let usedBands = [red, green, blue];
    if (step.bands === 'red') {
      usedBands = [red];
    } else if (step.bands === 'green') {
      usedBands = [green];
    } else if (step.bands === 'blue') {
      usedBands = [blue];
    }

    bands = bands.map((band) => {
      if (usedBands.indexOf(band) === -1) {
        return band;
      }
      if (step.operation === 'sigmoidal-contrast') {
        return sigmoidalContrast(band, step.contrast, step.bias);
      } else if (step.operation === 'gamma') {
        return gamma(band, step.value);
      }
      console.warning(`Unknown operation ${step.operation}`);
      return band;
    });
  }

  [red, green, blue] = bands.map(band => toOriginalArray(band, Uint8Array));
  blitChannels(canvas, width, height, red, green, blue);
}
let webGLRenderer = null;
if (WebGLRenderer.isSupported()) {
  webGLRenderer = new WebGLRenderer();
}

export function renderData(canvas, ...args) {
  // TODO: prefer rendering via webgl
  // const gl = create3DContext(canvas);
  if (webGLRenderer) {
    // return renderDataWebGl(canvas, gl, ...args);
    return webGLRenderer.render(canvas, ...args);
  }
  return renderData2d(canvas, ...args);
}
